# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: leon

import datetime
import functools
import typing

from coin.exchange.base.kr_rest.futures_product import (NativeFuturesProduct,
                                                        NormFuturesProduct,
                                                        gen_full_symbol,
                                                        parse_futures_product_symbol)
from coin.exchange.base.kr_rest.from_str import FromStrProductV1V3Mixin
from coin.exchange.okex.config import futures_group_symbol
from coin.exchange.okex.kr_rest.constants import quote_currency_list
import coin.exchange.okex_futures.kr_rest.contract_util as contract_util
from coin.exchange.okex_futures.kr_rest.currency import OkexCurrency
from coin.exchange.okex.kr_rest.constants import currency_list


def _parse_okex_futures_native_symbol_v3(native_symbol):
  assert isinstance(native_symbol, str), native_symbol
  assert '-' in native_symbol
  assert '_' not in native_symbol
  assert '.' not in native_symbol
  triple = native_symbol.split('-')
  assert len(triple) == 3, native_symbol
  res = dict(zip(('base', 'quote', 'expiry'), triple))
  assert res['base'] in currency_list, res['base']
  assert res['quote'] in quote_currency_list, res['quote']
  return res


class OkexFuturesProduct(FromStrProductV1V3Mixin, NativeFuturesProduct):
  _exchange = 'Okex'
  NativeCurrencyType = OkexCurrency

  def __init__(self,
               base: OkexCurrency,
               quote: OkexCurrency,
               expiry: typing.Union[datetime.datetime, datetime.date, str],
               contract_type: str):
    assert quote.currency in ('USD', 'USDT'), 'Only USD and USDT are supported as quote.'

    expiry_str_v1 = contract_util.to_native_expiry_str(expiry, contract_type)
    expiry_str_v3 = contract_util.to_native_expiry_str_v3(expiry, contract_type)
    native_symbol_v1 = '%s%s' % (base.native_currency_futures, expiry_str_v1)
    native_symbol_v3 = '%s-%s-%s' % (
        base.native_currency_futures, quote.native_currency_futures, expiry_str_v3)

    self._native_symbol_v1 = native_symbol_v1
    self._native_symbol_v3 = native_symbol_v3

    super().__init__(base=base, quote=quote, expiry=expiry, native_symbol=native_symbol_v1)
    self._subscription_symbol = '%s-%s.%s' % (base.currency, quote.currency, contract_type.upper())
    if quote.currency == 'USDT':
      self._native_subscription_symbol = self._native_symbol_v3
    else:
      self._native_subscription_symbol = '%s_%s' % (base.native_currency, contract_type)
    self._contract_type = contract_type
    self.order_symbol = '%s_usd' % base.native_currency

    self._full_symbol_v1 = gen_full_symbol(base=self.base,
                                           quote=self.quote,
                                           exchange=self.exchange,
                                           expiry=self.expiry,
                                           native_symbol=self._native_symbol_v1)
    self._full_symbol_v3 = gen_full_symbol(base=self.base,
                                           quote=self.quote,
                                           exchange=self.exchange,
                                           expiry=self.expiry,
                                           native_symbol=self._native_symbol_v3)

  def __repr__(self):
    return str(super().__repr__(self.contract_type))

  @property
  def contract_type(self):
    return self._contract_type

  @property
  def native_symbol_v1(self):
    return self._native_symbol_v1

  @property
  def native_symbol_v3(self):
    return self._native_symbol_v3

  @property
  def full_symbol_v1(self):
    return self._full_symbol_v1

  @property
  def full_symbol_v3(self):
    return self._full_symbol_v3

  def get_query_pair(self):
    """Return a pair like `btc_usd` and `this_week`, used as parameters in
    query API.
    """
    return ("%s_%s" % (self.base.native_currency, self.quote.native_currency), self.contract_type)

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromProductImpl(cls, product, current_datetime):
    assert isinstance(product, NormFuturesProduct), type(product)
    assert product.quote.currency in ('USD', 'USDT'), product.quote.currency
    contract_type = contract_util.find_contract_type_from_expiry(current_datetime, product.expiry)
    return cls(product.base, product.quote, product.expiry, contract_type)

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrProductImpl(cls, product, current_datetime):
    assert isinstance(product, str), product

    parsed_result = parse_futures_product_symbol(product)
    assert parsed_result['quote'] in ('USD', 'USDT'), parsed_result['quote']
    expiry_str = parsed_result['expiry'].lower()
    expiry, contract_type = contract_util.to_expiry_contract_type(expiry_str, current_datetime)

    base = cls.NativeCurrencyType.FromStrCurrency(parsed_result['base'])
    quote = cls.NativeCurrencyType.FromStrCurrency(parsed_result['quote'])
    return cls(base, quote, expiry, contract_type)

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrNativeProductV1Impl(cls, product, current_datetime):
    assert isinstance(product, str), product

    # Native symbol string.
    assert len(product) == 7, product
    base = product[:-4]
    assert base in currency_list
    expiry_str = product[-4:]
    expiry, contract_type = contract_util.parse_native_expiry_contract_type(
        expiry_str, current_datetime)

    base = cls.NativeCurrencyType.FromStrNativeCurrency(base)
    quote = cls.NativeCurrencyType.FromStrNativeCurrency('USD')
    return cls(base, quote, expiry, contract_type)

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrNativeProductV3Impl(cls, product, current_datetime):
    parsed_result = _parse_okex_futures_native_symbol_v3(product)
    assert parsed_result['quote'] in ('USD', 'USDT'), parsed_result
    base = cls.NativeCurrencyType.FromStrNativeCurrency(parsed_result['base'])
    quote = cls.NativeCurrencyType.FromStrNativeCurrency(parsed_result['quote'])
    expiry_str = parsed_result['expiry']
    expiry, contract_type = contract_util.parse_native_expiry_contract_type_v3(
        expiry_str, current_datetime)
    return cls(base, quote, expiry, contract_type)

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromCurrencyPair2AndContractTypeImpl(cls, base, quote, contract_type, current_datetime):
    expiry = contract_util.find_expiry_from_contract_type(current_datetime, contract_type)
    assert quote.currency == 'USD'
    return cls(base, quote, expiry, contract_type)

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromCurrencyPairAndContractTypeImpl(cls, currency_pair, contract_type, current_datetime):
    expiry = contract_util.find_expiry_from_contract_type(current_datetime, contract_type)
    pair = currency_pair.split('_')
    assert len(pair) == 2
    assert pair[1] == 'usd'
    base = OkexCurrency.FromStrNativeCurrency(pair[0])
    quote = OkexCurrency.FromStrCurrency('USD')
    return cls(base, quote, expiry, contract_type)

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromNativeSubscriptionSymbolV1(cls, native_subscription_symbol, current_datetime):
    exploded = native_subscription_symbol.split('_', 1)
    assert len(exploded) == 2
    base = OkexCurrency.FromStrNativeCurrency(exploded[0])
    quote = OkexCurrency.FromStrCurrency('USD')
    contract_type = exploded[1]
    return OkexFuturesProduct.FromCurrencyPair2AndContractType(base,
                                                               quote,
                                                               contract_type,
                                                               current_datetime)


def get_okex_futures_product_list(current_datetime=None):
  product_list_grouped = get_okex_futures_product_list_grouped(current_datetime)

  product_list = []
  for group in product_list_grouped:
    product_list += group
  return product_list


def get_okex_futures_product_list_grouped(current_datetime=None):
  product_list = []
  for group in futures_group_symbol.keys():
    product_list.append(get_okex_futures_product_list_for_group(group, current_datetime))
  return product_list


def get_okex_futures_product_list_for_group(group, current_datetime=None):
  product_list = []
  for symbol in futures_group_symbol[group]:
    if symbol.isupper():
      norm_subscription_symbol = symbol
      try:
        product = OkexFuturesProduct.FromStrProduct(norm_subscription_symbol, current_datetime)
        product_list.append(product)
      except Exception as e:
        print('Fail to generate Okex futures product.',
              '%s' % norm_subscription_symbol,
              type(e),
              e)
        continue
    else:
      native_subscription_symbol = symbol
      exploded = native_subscription_symbol.split('_', 1)
      assert len(exploded) == 2
      base = OkexCurrency.FromStrNativeCurrency(exploded[0])
      quote = OkexCurrency.FromStrCurrency('USD')
      contract_type = exploded[1]
      try:
        product = OkexFuturesProduct.FromCurrencyPair2AndContractType(
            base, quote, contract_type, current_datetime)
        product_list.append(product)
      except Exception as e:
        print('Fail to generate Okex futures product.',
              '%s%s.%s' % (base, quote, contract_type),
              type(e),
              e)
        continue
  return product_list
